/**
AirwayPathfinder for X-Plane Flight Simulator. Calculates the shortest path between two NavAids. 
Copyright (C) 2011  Nils Buletta

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 **/

package de.buletta.airwaypathfinder;

import java.util.ArrayList;
import org.jdesktop.swingx.mapviewer.Waypoint;
import org.jdesktop.swingx.mapviewer.GeoPosition;

/**
 * Represents a navigation point such as a VOR or NDB.
 * <p>
 * If an airway has an endpoint wich is not defined this point is represented
 * also as an EarthNav.
 * <p>
 * Note: this class has a natural ordering that is inconsistent with equals.
 * 
 * @author Nils Buletta
 * @version 0.1
 */
public class EarthNav extends Waypoint implements Comparable<EarthNav> {
	private int type;
	private int elevation;
	private int frequency;
	private int receptionRange;
	private String identifier;
	private String name;
	private double variation;
	private double moveCost;
	private double estimatedCost;
	private EarthNav predecessor;
	protected ArrayList<Connection> connections = new ArrayList<>();

	public EarthNav() {

	}

	public EarthNav(double lat, double longi) {
		super(lat, longi);
	}

	/**
	 * Constructs a new {@link EarthNav} object using the given parameter.
	 * 
	 * @param type
	 * @param position
	 * @param elevation
	 * @param frequency
	 * @param receptionRange
	 * @param identifier
	 * @param name
	 * @param variation
	 * @param connections
	 */
	public EarthNav(int type, GeoPosition position, int elevation,
			int frequency, int receptionRange, String identifier, String name,
			double variation, ArrayList<Connection> connections) {
		this.type = type;
		setPosition(position);
		this.elevation = elevation;
		this.frequency = frequency;
		this.receptionRange = receptionRange;
		this.identifier = identifier;
		this.name = name;
		this.variation = variation;
		this.connections = connections;
	}

	/**
	 * This is used to get a copy of the EarthNav objects that are used during
	 * the search because they reside in a HashMap and should therefore not be
	 * changed directly.
	 * <p>
	 * Note: The list of {@link Connection}s is only a shallow copy. If you
	 * change the connections of the copy you have to adjust this method first
	 * to preserve the integrity of the HashMap containing all the EarthNav
	 * objects.
	 * 
	 * 
	 * @return a copy of the {@code EarthNav} object
	 */
	public EarthNav getSearchCopy() {
		return new EarthNav(this.getType(), this.getPosition(),
				this.getElevation(), this.getFrequency(),
				this.getReceptionRange(), this.getIdentifier(), this.getName(),
				this.getVariation(), this.getConnections());
	}

	/**
	 * Returns the type of this {@code EarthNav} object.
	 * 
	 * @return - the type of this {@code EarthNav} object
	 */
	public int getType() {
		return type;
	}

	/**
	 * Sets the type of this {@code EarthNav} object.
	 * 
	 * @param type
	 *            - the type of this {@code EarthNav} object
	 */
	public void setType(int type) {
		this.type = type;
	}

	/**
	 * Returns the elevation of this nav point.
	 * 
	 * @return - the elevation
	 */
	public int getElevation() {
		return elevation;
	}

	/**
	 * Sets the elevation of this nav point.
	 * 
	 * @param elevation
	 *            - the elevation
	 */
	public void setElevation(int elevation) {
		this.elevation = elevation;
	}

	/**
	 * Returns the frequency of this nav point if one is set.
	 * 
	 * @return - the frequency of this nav point
	 */
	public int getFrequency() {
		return frequency;
	}

	/**
	 * Sets the frequency of this nav point.
	 * 
	 * @param frequency
	 *            - the frequency
	 */
	public void setFrequency(int frequency) {
		this.frequency = frequency;
	}

	/**
	 * Returns the reception range for this nav point.
	 * 
	 * @return - the reception range
	 */
	public int getReceptionRange() {
		return receptionRange;
	}

	/**
	 * Sets the reception range for this nav point.
	 * 
	 * @param receptionRange
	 *            - the reception range
	 */
	public void setReceptionRange(int receptionRange) {
		this.receptionRange = receptionRange;
	}

	/**
	 * Returns the identifier for this nav point
	 * 
	 * @return - the identifier
	 */
	public String getIdentifier() {
		return identifier;
	}

	/**
	 * Sets the identifier for this nav point
	 * 
	 * @param identifier
	 *            - the identifier
	 */
	public void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	/**
	 * Returns the name of this nav point
	 * 
	 * @return - the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of this nav point.
	 * 
	 * @param name
	 *            - the name of this nav point
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Returns the variation for this nav point.
	 * 
	 * @return - the variation
	 */
	public double getVariation() {
		return variation;
	}

	/**
	 * Sets the variation for this nav point.
	 * 
	 * @param variation
	 *            - the variation
	 */
	public void setVariation(double variation) {
		this.variation = variation;
	}

	/**
	 * Add a connection from this point to the point specified in the
	 * {@link Connection} object.
	 * 
	 * @param c
	 *            the Connection
	 * @see Connection
	 */
	public void addConnection(Connection c) {
		for (Connection con : connections) {
			if (con.getPosition().equals(c.getPosition())) {
				con.setLayer(con.getLayer() + c.getLayer());
				return;
			}
		}
		this.connections.add(c);
	}

	/**
	 * Returns a list of the connections this {@code EarthNav} object is part
	 * of.
	 * 
	 * @return - an {@code ArrayList} containing the connections
	 */
	public ArrayList<Connection> getConnections() {
		return this.connections;
	}

	/**
	 * Returns the overall cost (estimated cost + move cost) of this
	 * {@code EarthNav} object.
	 * <p>
	 * Only used during path calculation.
	 * 
	 * @return - the overall cost
	 */
	public double getCost() {
		return this.moveCost + this.estimatedCost;
	}

	/**
	 * Sets the cost to move from one {@code EarthNav} object to this one.
	 * 
	 * @param m
	 *            - the move cost
	 */
	public void setMoveCost(double m) {
		this.moveCost = m;
	}

	/**
	 * Returns the move cost.
	 * 
	 * @return - the move cost
	 */
	public double getMoveCost() {
		return moveCost;
	}

	/**
	 * Sets the estimated cost for this {@code EarthNav} object.
	 * <p>
	 * The estimated cost is the distance from this position to the destination.
	 * <p>
	 * Only used during path calculation
	 * 
	 * @param e
	 *            - the estimated cost
	 */
	public void setEstimatedCost(double e) {
		this.estimatedCost = e;
	}

	/**
	 * Returns the estimated cost.
	 * 
	 * @return the estimated cost
	 */
	public double getEstimatedCost() {
		return estimatedCost;
	}

	/**
	 * Sets the predecessor of this {@code EarthNav} object.
	 * 
	 * @param p
	 *            - the predecessor
	 */
	public void setPredecessor(EarthNav p) {
		this.predecessor = p;
	}

	/**
	 * Returns the predecessor.
	 * 
	 * @return - the predecessor
	 */
	public EarthNav getPredecessor() {
		return predecessor;
	}

	/**
	 * Only non-changing fields from the {@link EarthNav} object are considered
	 * to compute the hashcode.
	 * 
	 * @return a hashcode value for this object.
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + elevation;
		result = prime * result + frequency;
		result = prime * result
				+ ((identifier == null) ? 0 : identifier.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((getPosition() == null) ? 0 : getPosition().hashCode());
		result = prime * result + receptionRange;
		result = prime * result + type;
		long temp;
		temp = Double.doubleToLongBits(variation);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	/**
	 * This method checks if an object is equal to this {@link EarthNav} object.
	 * <p>
	 * Only the non-changing fields of the {@link EarthNav} objects are
	 * considered.
	 * 
	 * @param obj
	 *            the object to compare to
	 * 
	 * @return true if this object is the same as the obj argument; false
	 *         otherwise.
	 * 
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof EarthNav)) {
			return false;
		}
		EarthNav other = (EarthNav) obj;
		if (elevation != other.elevation) {
			return false;
		}
		if (frequency != other.frequency) {
			return false;
		}
		if (identifier == null) {
			if (other.identifier != null) {
				return false;
			}
		} else if (!identifier.equals(other.identifier)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		if (getPosition() == null) {
			if (other.getPosition() != null) {
				return false;
			}
		} else if (!getPosition().equals(other.getPosition())) {
			return false;
		}
		if (receptionRange != other.receptionRange) {
			return false;
		}
		if (type != other.type) {
			return false;
		}
		if (Double.doubleToLongBits(variation) != Double
				.doubleToLongBits(other.variation)) {
			return false;
		}
		return true;
	}

	/**
	 * Compares to EarthNav objects. Compared are the overall cost computed
	 * during the pathfinding
	 * <p>
	 * Note: this class has a natural ordering that is inconsistent with equals.
	 * 
	 * @param other
	 *            the EarthNav object to compare to
	 * 
	 * @return -1 if other is less, 0 if other is equal, 1 if other is greater
	 *         than this
	 * 
	 */
	@Override
	public int compareTo(EarthNav other) {
		if (this.getCost() > other.getCost()) {
			return 1;
		}

		if (this.getCost() < other.getCost()) {
			return -1;
		}

		return 0;
	}
}